package com.youth.oauth.config.security;

import com.youth.oauth.config.ldap.LdapConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;

/**
 * Security配置类
 * 该类用于配置Spring Security，包括认证、授权和过滤器链的配置
 */
@EnableMethodSecurity  // 开启方法权限
@Configuration
public class SecurityConfig {

    @Resource
    LdapConfig ldapConfig;

    @Resource
    LoginUnAuthenticationEntryPointHandler loginUnAuthenticationEntryPointHandler;

    @Resource
    RestAuthenticationFailureHandler restAuthenticationFailureHandler;

    @Resource
    JwtAuthenticationFilter jwtAuthenticationFilter;

    @Resource
    LogoutStatusSucessHandler logoutStatusSucessHandler;

    @Resource
    LoginUnAccessDeniedHandler loginUnAccessDeniedHandler;

    /**
     * 配置Security过滤链
     * 该方法用于定义如何配置Web安全约束，包括认证要求、登录和注销的规则，以及跨站请求伪造(CSRF)保护的禁用
     *
     * @param httpSecurity 用于配置Web安全的HttpSecurity对象
     * @return SecurityFilterChain 返回配置好的Security过滤链
     * @throws Exception 配置过程中可能抛出的异常
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        // 配置HttpSecurity以禁用CSRF保护和表单登录，并自定义异常处理和注销逻辑
        httpSecurity
                .csrf().disable() // 禁用CSRF保护，因为我们将在无状态API中使用JWT进行身份验证
                .formLogin().disable() // 禁用表单登录，因为我们将在无状态API中使用JWT进行身份验证
                .authorizeRequests()
                .antMatchers("/login").permitAll() // 登录接口放行，允许未认证的用户访问
                .anyRequest().authenticated(); // 所有其他请求都需要用户进行身份验证

        // 在UsernamePasswordAuthenticationFilter之前添加JWT身份验证过滤器
        httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        // 配置异常处理，设置未认证和无访问权限时的处理方式
        httpSecurity.exceptionHandling().authenticationEntryPoint(loginUnAuthenticationEntryPointHandler);
        httpSecurity.exceptionHandling().accessDeniedHandler(loginUnAccessDeniedHandler);

        // 配置注销逻辑，设置注销成功后的处理方式
        httpSecurity.logout().logoutSuccessHandler(logoutStatusSucessHandler);

        // 构建HttpSecurity配置
        return httpSecurity.build();
    }


    /**
     * 配置并创建一个Active Directory LDAP认证提供者
     * 该方法初始化一个ActiveDirectoryLdapAuthenticationProvider实例，用于Spring Security进行LDAP认证
     * 主要负责设置认证提供者所需的域和LDAP URL，并配置一些认证选项
     *
     * @return AuthenticationProvider 认证提供者实例，用于用户认证
     */
    @Bean
    public AuthenticationProvider authenticationProvider() {
        // 创建Active Directory LDAP认证提供者，传入域和LDAP URL作为参数
        ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider
                (ldapConfig.getAdDomain(), ldapConfig.getLdapUrl());

        /**
         * 启用子错误码转换为异常
         * 设置是否将Active Directory返回的子错误码转换为对应的异常
         * 这有助于更详细地了解认证失败的原因，默认为false
         */
        provider.setConvertSubErrorCodesToExceptions(true);

        /**
         * 启用身份验证请求凭据，确保用户凭证在认证过程中被正确传递 以便 Active Directory 可以使用它来验证用户
         * 默认为false，启用后可以提高认证的准确性和安全性
         */
        provider.setUseAuthenticationRequestCredentials(true);

        // 返回配置好的认证提供者实例
        return provider;
    }

    /**
     * 获取认证管理器
     *
     * 本方法通过注入的认证配置对象来获取认证管理器对象，主要用于安全配置中
     *
     * @param authenticationConfiguration 认证配置对象，用于获取认证管理器
     * @return 认证管理器对象，用于处理认证逻辑
     * @throws Exception 如果获取认证管理器过程中出现错误，会抛出异常
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
            throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

}
